<?php
/**
 * HTTP客户端实现类模块
 * 
 * @author jlpay-sdk-php
 * @version 1.0.0
 */

namespace JLPay\Core\Http;

// 确保异常类被加载
require_once __DIR__ . '/HttpExceptions.php';

use Exception;
use Throwable;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use JLPay\Exceptions\HttpExecutionException;

/**
 * HTTP客户端实现类
 */
class HttpClientWrapper implements HttpClient
{
    /**
     * 连接超时时间（秒）
     * @var float
     */
    private $connectTimeout;
    
    /**
     * 读取超时时间（秒）
     * @var float
     */
    private $readTimeout;
    
    /**
     * 最大重试次数
     * @var int
     */
    private $maxRetries;
    
    /**
     * 日志记录器
     * @var LoggerInterface
     */
    private $logger;
    
    /**
     * 可重试的异常类名
     * @var array
     */
    private $retryableExceptions = [
        'RuntimeException',
        'Exception'
    ];
    
    /**
     * 初始化HTTP客户端
     * 
     * @param int $connectTimeout 连接超时时间，单位毫秒
     * @param int $readTimeout 读取超时时间，单位毫秒
     * @param int $maxRetries 最大重试次数
     * @param LoggerInterface|null $logger 日志记录器
     */
    public function __construct($connectTimeout = 10000, $readTimeout = 30000, $maxRetries = 3, LoggerInterface $logger = null)
    {
        $this->connectTimeout = $connectTimeout / 1000;  // 转换为秒
        $this->readTimeout = $readTimeout / 1000;  // 转换为秒
        $this->maxRetries = $maxRetries;
        $this->logger = $logger ?: new NullLogger();
    }
    
    /**
     * 执行HTTP请求
     * 
     * @param HttpRequest $request HttpRequest对象
     * @return HttpResponse HttpResponse对象
     * @throws HttpExecutionException 执行异常
     * @throws HttpStatusCodeException 状态码异常
     */
    public function execute(HttpRequest $request)
    {
        $retryCount = 0;
        $lastException = null;
        
        while ($retryCount <= $this->maxRetries) {
            try {
                $headers = $request->headers;
                $url = $request->url;
                $method = $request->method;
                $params = $request->params;
                $body = $request->body;
                $files = $request->files;
                
                // 记录请求开始日志
                $retryInfo = $retryCount > 0 ? " (重试 #{$retryCount})" : '';
                $this->logger->debug("发起HTTP请求: {$method} {$url}{$retryInfo}");
                $this->logger->debug("请求头: " . json_encode($headers, JSON_UNESCAPED_UNICODE));
                
                if ($params) {
                    $this->logger->debug("请求参数: " . json_encode($params, JSON_UNESCAPED_UNICODE));
                }
                if ($body) {
                    $this->logger->debug("请求体: " . (is_string($body) ? $body : json_encode($body, JSON_UNESCAPED_UNICODE)));
                }
                
                $startTime = microtime(true);
                
                // 执行请求
                $response = $this->performRequest($method, $url, $headers, $params, $body, $files);
                
                // 计算请求耗时
                $elapsedTime = (microtime(true) - $startTime) * 1000;  // 转换为毫秒
                
                // 记录响应日志
                $this->logger->debug("HTTP响应状态码: {$response->status}, 耗时: " . number_format($elapsedTime, 2) . "ms");
                $this->logger->debug("响应头: " . json_encode($response->headers, JSON_UNESCAPED_UNICODE));
                
                // 检查状态码
                if ($response->status !== 200) {
                    $this->logger->error("HTTP响应异常状态码: {$response->status}, 响应内容: {$response->body}");
                    throw new HttpStatusCodeException($response->status, $response->body);
                }
                
                // 记录响应体日志（仅在调试级别）
                $this->logger->debug("响应体: {$response->body}");
                
                return $response;
                
            } catch (HttpStatusCodeException $e) {
                // 状态码异常不重试
                throw $e;
            } catch (Throwable $e) {
                $lastException = $e;
                $retryCount++;
                
                if ($this->shouldRetry($e) && $retryCount <= $this->maxRetries) {
                    $waitTime = 2 ** $retryCount;  // 指数退避策略
                    $this->logger->warning("HTTP请求遇到错误: " . $e->getMessage() . ", 正在进行第 {$retryCount} 次重试, 等待 {$waitTime} 秒...");
                    sleep($waitTime);
                } else {
                    $this->logger->error("HTTP请求重试 {$this->maxRetries} 次后仍然失败: " . $e->getMessage());
                    throw new HttpExecutionException("HTTP请求执行异常: " . $e->getMessage(), 0, $e);
                }
            }
        }
        
        // 如果所有重试都失败了
        if ($lastException) {
            $this->logger->error("HTTP请求重试 {$this->maxRetries} 次后仍然失败: " . $lastException->getMessage());
            throw new HttpExecutionException("HTTP请求重试失败: " . $lastException->getMessage(), 0, $lastException);
        }
        
        throw new HttpExecutionException("HTTP请求失败，未知错误");
    }
    
    /**
     * 执行实际的HTTP请求
     * 
     * @param string $method HTTP方法
     * @param string $url 请求URL
     * @param array $headers 请求头
     * @param array|null $params URL参数
     * @param mixed $body 请求体
     * @param array|null $files 文件
     * @return HttpResponse
     * @throws Exception
     */
    private function performRequest($method, $url, $headers, $params, $body, $files)
    {
        // 如果有参数，添加到URL
        if ($params && is_array($params)) {
            $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($params);
        }
        
        // 初始化cURL
        $ch = curl_init();
        
        // 设置基本选项
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => true,
            CURLOPT_CONNECTTIMEOUT_MS => (int)($this->connectTimeout * 1000),
            CURLOPT_TIMEOUT_MS => (int)($this->readTimeout * 1000),
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 5,
        ]);
        
        // 设置请求方法
        switch (strtoupper($method)) {
            case 'GET':
                break;
            case 'POST':
                curl_setopt($ch, CURLOPT_POST, true);
                break;
            case 'PUT':
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
                break;
            case 'DELETE':
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
                break;
            default:
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
                break;
        }
        
        // 设置请求头
        if (!empty($headers)) {
            $curlHeaders = [];
            foreach ($headers as $key => $value) {
                $curlHeaders[] = "{$key}: {$value}";
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders);
        }
        
        // 设置请求体
        if ($files && is_array($files)) {
            // 文件上传
            curl_setopt($ch, CURLOPT_POSTFIELDS, $files);
        } elseif ($body !== null) {
            if (is_array($body) || is_object($body)) {
                // 如果Content-Type是JSON，转换为JSON字符串
                $contentType = $headers['Content-Type'] ?? '';
                if (strpos($contentType, 'application/json') !== false) {
                    $bodyData = json_encode($body, JSON_UNESCAPED_UNICODE);
                } else {
                    $bodyData = http_build_query($body);
                }
            } else {
                $bodyData = $body;
            }
            curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyData);
        }
        
        // 执行请求
        $response = curl_exec($ch);
        
        // 检查cURL错误
        if ($response === false) {
            $error = curl_error($ch);
            $errno = curl_errno($ch);
            curl_close($ch);
            throw new Exception("cURL错误: {$error} (错误码: {$errno})");
        }
        
        // 获取HTTP状态码
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        
        curl_close($ch);
        
        // 分离响应头和响应体
        $headerString = substr($response, 0, $headerSize);
        $responseBody = substr($response, $headerSize);
        
        // 解析响应头
        $responseHeaders = $this->parseHeaders($headerString);
        
        // 构建响应对象
        $httpResponse = new HttpResponse();
        $httpResponse->body = $responseBody;
        $httpResponse->status = $httpCode;
        $httpResponse->headers = $responseHeaders;
        
        return $httpResponse;
    }
    
    /**
     * 解析响应头
     * 
     * @param string $headerString 响应头字符串
     * @return array
     */
    private function parseHeaders($headerString)
    {
        $headers = [];
        $lines = explode("\r\n", $headerString);
        
        foreach ($lines as $line) {
            if (strpos($line, ':') !== false) {
                list($key, $value) = explode(':', $line, 2);
                $headers[trim($key)] = trim($value);
            }
        }
        
        return $headers;
    }
    
    /**
     * 判断异常是否应该重试
     * 
     * @param Throwable $exception 异常对象
     * @return bool
     */
    private function shouldRetry(Throwable $exception)
    {
        // 检查是否是可重试的异常类型
        $exceptionClass = get_class($exception);
        foreach ($this->retryableExceptions as $retryableClass) {
            if ($exception instanceof $retryableClass || $exceptionClass === $retryableClass) {
                return true;
            }
        }
        
        // 检查cURL错误码
        $message = $exception->getMessage();
        $retryableMessages = [
            'Connection timed out',
            'Operation timed out',
            'Failed to connect',
            'Couldn\'t connect to server',
            'Empty reply from server',
            'Recv failure',
            'Send failure'
        ];
        
        foreach ($retryableMessages as $retryableMessage) {
            if (strpos($message, $retryableMessage) !== false) {
                return true;
            }
        }
        
        return false;
    }
} 